package org.hepeng.workx.spring.cloud.netflix.zuul.route;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.hepeng.workx.spring.context.ApplicationContextHolder;
import org.hepeng.workx.web.http.BodyCachingHttpServletRequestWrapper;
import org.hepeng.workx.web.util.HttpRequestUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.netflix.zuul.filters.Route;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
import org.springframework.util.Assert;
import org.tuckey.web.filters.urlrewrite.extend.RewriteMatch;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * @author he peng
 */
public abstract class AbstractZuulRoutingRewriteMatch extends RewriteMatch {

    private static final Logger LOG = LoggerFactory.getLogger(AbstractZuulRoutingRewriteMatch.class);
    public static final String TARGET_SERVICE_REQUEST_ATTR = AbstractZuulRoutingRewriteMatch.class.getName() + ".target.service.name";
    public static final String REWRITE_MATCH_CLASS_REQUEST_ATTR = AbstractZuulRoutingRewriteMatch.class.getName() + ".RewriteMatch.ATTR";

    protected HttpServletRequest request;
    protected String originalUri;
    protected List<Route> routes;
    protected List<String> routePrefixes = new ArrayList<>();
    protected ZuulProperties zuulProperties;
    private String routeListMsg;

    public AbstractZuulRoutingRewriteMatch(HttpServletRequest request , String originalUri, List<Route> routes) {
        Assert.notNull(request, "request must not be null");
        Assert.hasLength(originalUri, "originalUri must not be blank");
        Assert.notNull(routes, "routes must not be null");

        this.request = request;
        this.originalUri = originalUri;
        this.routes = routes;

        this.zuulProperties = ApplicationContextHolder.getApplicationContext().getBean(ZuulProperties.class);
        StringBuilder routeListMsg = new StringBuilder("Route List : ").append("\r\n");
        for (Route route : routes) {
            this.routePrefixes.add(route.getPrefix());
            routeListMsg.append("  route id : ").append(route.getId())
                    .append(" , full path : ").append(route.getFullPath()).append("\r\n");
        }
        this.routeListMsg = routeListMsg.toString();
    }

    @Override
    public String getMatchingUrl() {

        String matchingUrl = null;
        if (CollectionUtils.isNotEmpty(routes)) {
            String appId = getApplicationId();
            appId = StringUtils.isBlank(appId) ? "" : appId;
            String contextPath = this.request.getServletContext().getContextPath();
            for (Route route : routes) {
                if (matchRoute(route , appId)) {
                    String prefix = route.getPrefix();
                    if (! StringUtils.startsWith(prefix , "/")) {
                        prefix += "/";
                    }
                    matchingUrl = contextPath + prefix + StringUtils.removeFirst(this.originalUri , contextPath);
                    this.request.setAttribute(TARGET_SERVICE_REQUEST_ATTR , route.getId());
                    break;
                }
            }
        }
        return matchingUrl;
    }

    protected abstract boolean matchRoute(Route route , String appId);

    protected abstract String getApplicationId();

    @Override
    public boolean execute(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        String matchingUrl = getMatchingUrl();

        StringBuilder msgBuilder = new StringBuilder("\r\n ************************* Url Rewrite ************************* \r\n")
                .append("Original Request URI : ").append(this.originalUri).append("\r\n")
                .append("RewriteMatch Class : ").append(
                        StringUtils.defaultString(
                                (String) request.getAttribute(REWRITE_MATCH_CLASS_REQUEST_ATTR),
                                "[Does not match any RewriteMatch]")).append("\r\n")
                .append("Rewritten Request URI : ");


        request.removeAttribute(REWRITE_MATCH_CLASS_REQUEST_ATTR);
        boolean isMultipart = HttpRequestUtils.isMultipart(request);
        if (StringUtils.isNotBlank(matchingUrl) || isMultipart) {
            if (isMultipart && StringUtils.startsWith(this.originalUri , this.zuulProperties.getServletPath())) {
                msgBuilder.append("[No need to rewrite]").append("\r\n").append(this.routeListMsg);
                LOG.info(msgBuilder.toString());
                return false;
            }

            if (isMultipart) {
                if (StringUtils.isNotBlank(matchingUrl)) {
                    if (! StringUtils.startsWith(matchingUrl , this.zuulProperties.getServletPath())) {
                        matchingUrl = this.zuulProperties.getServletPath() + matchingUrl;
                    }
                } else {
                    boolean isMatcheRoute = false;
                    for (String routePrefix : this.routePrefixes) {
                        if (StringUtils.startsWith(this.originalUri , routePrefix)) {
                            isMatcheRoute = true;
                            break;
                        }
                    }

                    if (! isMatcheRoute) {
                        msgBuilder.append("[No need to rewrite]").append("\r\n").append(this.routeListMsg);
                        LOG.info(msgBuilder.toString());
                        return false;
                    }

                    matchingUrl = this.zuulProperties.getServletPath() + this.originalUri;
                }
                request = new BodyCachingHttpServletRequestWrapper(request);
            }

            msgBuilder.append(matchingUrl).append("\r\n").append(this.routeListMsg);

            LOG.info(msgBuilder.toString());
            RequestDispatcher dispatcher = request.getRequestDispatcher(matchingUrl);
            dispatcher.forward(request , response);
            return true;
        } else {
            msgBuilder.append("[No need to rewrite]").append("\r\n").append(this.routeListMsg);
            LOG.info(msgBuilder.toString());
            return false;
        }
    }
}
